#include <stdio.h>
#include <ctype.h>
#include "defs.h"
#include "externs.h"
#include "protos.h"

/* defines */
#define SND_STOP            0    /* stop processing (track finish) */
#define SND_OFF             1    /* mute sound */
#define SND_ON              2    /* turn on sound */
#define SND_VOLUME          3    /* set voice volume (left & right) */
#define SND_FREQ            4    /* set voice frequency */
#define SND_DURATION        5    /* set voice duration */
#define SND_NOISE_FREQ      6    /* set noise frequency */
#define SND_NOISE_OFF       7    /* set noise off */
#define SND_LFO_FREQ        8    /* set LFO frequency */
#define SND_LFO_CTRL        9    /* set LFO control */
#define SND_WAVE_SINE       20    /* init to sine wave (predefined) */
#define SND_WAVE_SAW        21    /* init to sawtooth wave (predefined) */
#define SND_WAVE_SQR        22    /* init to square wave (predefined) */
#define SND_WAVE_DATA       30    /* init to inline wave data */

/* locals */
static unsigned int snd_wave;
static unsigned int snd_octave;
static unsigned int snd_volume;
static unsigned int snd_length;
static unsigned int snd_tempo;
static unsigned int snd_timebase;
static unsigned int snd_ticks_h, snd_ticks_l;
static int snd_wave_flag;
static int snd_off;
static int tone_table[7]  = { 10, 12, 1, 3, 5, 6, 8 };
static int freq_table[14] = {
    0x001EDD,
    0x0020B3, 0x0022A6,
    0x0024B5, 0x0026E3,
    0x002933,
    0x002BA6, 0x002E40,
    0x003100, 0x0033E8,
    0x003700, 0x003A45,
    0x003DBA,
    0x004166
};

/* protos */
int mml_get_value(char **ptr);
int mml_get_length(char **ptr);
int mml_calc_duration(unsigned int len);


/* ----
 * mml_start()
 * ----
 */

int
mml_start(unsigned char *buffer)
{
    *buffer = SND_OFF;

    snd_off = 1;
    snd_wave_flag = 0;
    snd_octave = 4;
    snd_volume = 15;
    snd_length = 4;
    snd_tempo = 128;
    snd_timebase = 60;
    snd_ticks_h = 0;
    snd_ticks_l = 0;

    /* ok */
    return (1);
}


/* ----
 * mml_stop()
 * ----
 */

int
mml_stop(unsigned char *buffer)
{
    *buffer = SND_STOP;

    return (1);
}


/* ----
 * mml_parse()
 * ----
 */

int
mml_parse(unsigned char *buffer, int bufsize, char *ptr)
{
    unsigned int tone, freq, value, len;
    int size = 0;
    unsigned char c;

    /* parse string */
    while ((c = *ptr++) != '\0') {
        /* check buffer size */
        if (bufsize < 7) {
            fatal_error("Internal error: MML buffer too small!");
            return (-1);
        }

        switch (c) {
        /* octave */
        case 'O':
            snd_octave = mml_get_value(&ptr);
    
            if ((snd_octave < 1) || (snd_octave > 7)) {
                error("Incorrect octave!");
                return (-1);
            }
            break;
    
        /* volume */
        case 'V':
            snd_volume = mml_get_value(&ptr);
    
            if (snd_volume > 15) {
                error("Incorrect volume!");
                return (-1);
            }
    
            /* gen code */
            *buffer++ = SND_VOLUME;
            *buffer++ = (snd_volume << 4) + snd_volume;
            size += 2;
            break;

        /* tempo */
        case 'T':
            snd_tempo = mml_get_value(&ptr);
    
            if ((snd_tempo < 32) || (snd_tempo > 256)) {
                error("Incorrect tempo!");
                return (-1);
            }
            break;
    
        /* length */
        case 'L':
            snd_length = mml_get_length(&ptr);
    
            if (!snd_length) {
                error("Incorrect note length!");
                return (-1);
            }
            break;
    
        /* notes */
        case 'A':
        case 'B':
        case 'C':
        case 'D':
        case 'E':
        case 'F':
        case 'G':
            /* base */
            tone = tone_table[c - 'A'];

            /* extra commands */
            if ((*ptr == '#') || (*ptr == '+')) {
                tone++;
                ptr++;
            }
            else if (*ptr == '-') {
                tone--;
                ptr++;
            }
            if (isdigit(*ptr))
                len = (mml_get_length(&ptr) << 8);
            else {
                len = (snd_length << 8);
            }
            if (*ptr == '.') {
                len = (len * 0xC0) >> 8;
                ptr++;
            }

            /* check length */
            if (!len) {
                error("Incorrect note length!");
                return (-1);
            }

            /* calculate frequency */
            freq  = (freq_table[tone] << (snd_octave - 1));
            value = (((3580000 * 16) / freq) + 1) >> 1;

            /* gen code */
            *buffer++ = SND_FREQ;
            *buffer++ = (value)  & 0xFF;
            *buffer++ = (value >> 8) & 0xFF;

            if (snd_wave_flag) {
                /* new waveform */
                snd_wave_flag = 0;
                snd_off = 0;
               *buffer++ = SND_WAVE_SINE + (snd_wave - 1);
                size += 1;
            }
            else {
                if (snd_off) {
                    /* sound on */
                    snd_off = 0;
                   *buffer++ = SND_ON;
                    size += 1;
                }
            }

            *buffer++ = SND_DURATION;
            *buffer++ = mml_calc_duration(len);
            size += 5;
            break;
    
        /* rest */
        case 'R':
            /* local length */
            if (isdigit(*ptr))
                len = (mml_get_length(&ptr) << 8);
            else {
                len = (0x0400);
            }
            if (*ptr == '.') {
                len = (len * 0xC0) >> 8;
                ptr++;
            }

            /* check length */
            if (!len) {
                error("Incorrect note length!");
                return (-1);
            }

            /* gen code */
            *buffer++ = SND_OFF;
            *buffer++ = SND_DURATION;
            *buffer++ = mml_calc_duration(len);
            snd_off = 1;
            size += 3;
            break;

        /* waveform */
        case 'W':
            snd_wave = mml_get_value(&ptr);
            snd_wave_flag = 1;
    
            if ((snd_wave < 1) || (snd_wave > 3)) {
                error("Incorrect waveform!");
                return (-1);
            }
            break;
    
        /* other */
        default:
            error("Syntax error!");
            return (-1);
        }

        /* update size */
        bufsize -= size;
    }

    /* ok */
    return (size);
}


/* ----
 * mml_get_value()
 * ----
 */

int
mml_get_value(char **ptr)
{
    unsigned int value;
    unsigned char c;

    /* get value */
    value = 0;

    for (;;) {
        c = **ptr;

        if (!isdigit(c))
            break;
        if (value > 65535)
            return (-1);

        value = (value * 10) + (c - '0');
        (*ptr)++;
    }

    /* ok */
    return (value);
}


/* ----
 * mml_get_length()
 * ----
 */

int
mml_get_length(char **ptr)
{
    int len;

    len = mml_get_value(ptr);

    switch (len) {
    case  1:
    case  2:
    case  3:
    case  4:
    case  6:
    case  8:
    case 12:
    case 16:
    case 24:
    case 32:
    case 48:
    case 64:
    case 96:
        return (len);

    default:
        return (0);
    }
}


/* ----
 * mml_calc_duration()
 * ----
 */

int
mml_calc_duration(unsigned int len)
{
    unsigned int ticks, mask, tmp;

    /* ticks */
    ticks = 0xC00000 / len;

    /* base */
    if (snd_timebase == 0) {
        /* timer */
        mask = 0xFF;
        snd_ticks_h = (ticks >> 8);
        snd_ticks_l = (ticks & 0xFF) + snd_ticks_l;
    }
    else {
        /* fixed frequency (ie. vsync) */
        mask = 0xFFFFFF;
        tmp  = (256 - snd_tempo) * snd_timebase * ticks * 8;
        snd_ticks_h = (tmp >> 24);
        snd_ticks_l = (tmp & 0xFFFFFF) + snd_ticks_l;
    }

    /* adjust timings */
    if (snd_ticks_l  > mask) {
        snd_ticks_l &= mask;
        snd_ticks_h++;
    }
    if (snd_ticks_h == 0)
        snd_ticks_h = 1;

    /* result */
    return (snd_ticks_h - 1);
}

